﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Xml;
using SharpObjects.Authorization.Properties;
using SharpObjects.Configuration;
using SharpObjects.Configuration.ConfigurationProviders;
using SharpObjects.Configuration.Utils;
using System.Security.Principal;
using SharpObjects.Data;
using SharpObjects.Data.EntityMapping;
using SharpObjects.Instantiation;
using SharpObjects.Instantiation.Services;

namespace Authorization
{
    public class AccessControlReader : IDisposable
    {
        protected XmlDefinitions sql;
        protected IDb db = null;

        public AccessControlReader()
        {
            this.db = ServiceFactory.Instance.GetService<IDb>("authorization_database");
            InitSql();
        }

        protected void InitSql()
        {
            string accessControlSqlConfig = Resources.accessControlSqlFile;
            string accessControlSqlXml = Resources.AccessControlSql;

            if (File.Exists(accessControlSqlConfig))
            {
                accessControlSqlXml = File.ReadAllText(accessControlSqlConfig);
            }

            XmlDocument document = new XmlDocument();
            document.LoadXml(accessControlSqlXml);

            this.sql = new XmlDefinitions(document.DocumentElement, this.db.ResourceVersion);
        }

        public bool IsAuthorized(IPrincipal user, string resourceName)
        {
            List<AccessControlEntry> acls = GetACLsByResourceName(resourceName);

            bool isGranted = false;
            bool isDenied = false;

            foreach (AccessControlEntry acl in acls)
            {
                if(acl.DenyAccess)
                {
                    if(user.IsInRole(acl.AccessorName) || user.Identity.Name == acl.AccessorName)
                    {
                        isDenied = true;
                    }
                }
                else
                {
                    if(user.IsInRole(acl.AccessorName) || user.Identity.Name == acl.AccessorName)
                    {
                        isGranted = true;
                    }
                }
            }

            if(isGranted && !isDenied)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool IsAuthorized(string userName, List<string> roles)
        {
            return false;
        }

        public int GetResourceIdByName(string resourceName)
        {
            string queryTemplate = this.sql[SqlTags.select_resource_id_by_name];
            IDbCommand command = this.db.CreateQueryCommand(queryTemplate, new DbParameterDescriptor("name", eDataType.UnicodeString, ParameterDirection.Input, 0, resourceName));
            object obj = this.db.ExecuteScalar(command);

            if(obj != null)
            {
                return (int)obj;
            }
            else
            {
                throw new ArgumentException(string.Format("No resource with the name '{0}' found", resourceName));
            }
        }

        public List<AccessControlEntry> GetACLsByResourceId(int resourceId)
        {
            string queryTemplate = this.sql[SqlTags.select_acls_by_id];
            IDbCommand command = this.db.CreateQueryCommand(queryTemplate, new DbParameterDescriptor("resource_id", eDataType.Int, ParameterDirection.Input, 0, resourceId));
            IDbDataAdapter dataAdapter = db.CreateDataAdapter(command);
            DataSet dataSet = db.FillDataSet(dataAdapter);

            IEntityFactory entityFactory = new EntityFactory();
            List<AccessControlEntry> acls = entityFactory.GetObjects<AccessControlEntry>(dataSet.Tables[0].Rows);

            return acls;
        }

        public List<AccessControlEntry> GetACLsByResourceName(string resourceName)
        {
            string queryTemplate = this.sql[SqlTags.select_acls_by_resource_name];
            IDbCommand command = this.db.CreateQueryCommand(queryTemplate, new DbParameterDescriptor("name", eDataType.UnicodeString, ParameterDirection.Input, 0, resourceName));
            IDbDataAdapter dataAdapter = db.CreateDataAdapter(command);
            DataSet dataSet = db.FillDataSet(dataAdapter);

            IEntityFactory entityFactory = new EntityFactory();
            List<AccessControlEntry> acls = entityFactory.GetObjects<AccessControlEntry>(dataSet.Tables[0].Rows);

            return acls;
        }

        public List<AccessControlEntry> GetACLsByResourceNameAndAccessorName(string resourceName, string accessorName)
        {
            string queryTemplate = this.sql[SqlTags.select_acls_by_resource_name_and_accessor_name];
            IDbCommand command = this.db.CreateQueryCommand(queryTemplate, 
                new DbParameterDescriptor("name", eDataType.UnicodeString, ParameterDirection.Input, 0, resourceName),
                new DbParameterDescriptor("accessor_name", eDataType.UnicodeString, ParameterDirection.Input, 0, accessorName));
            IDbDataAdapter dataAdapter = db.CreateDataAdapter(command);
            DataSet dataSet = db.FillDataSet(dataAdapter);

            IEntityFactory entityFactory = new EntityFactory();
            List<AccessControlEntry> acls = entityFactory.GetObjects<AccessControlEntry>(dataSet.Tables[0].Rows);

            return acls;
        }


        public void Close()
        {
            lock(this)
            {
                if(this.db != null)
                {
                    this.db.Dispose();
                    this.db = null;
                }
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            Close();
        }

        #endregion
    }
}

//======================= DB SCHEMA: ====================================

//USE [TaskManagement]
//GO

///****** Object:  DbTable [dbo].[AccessControlResources]    Script Date: 8/30/2012 3:36:58 PM ******/
//SET ANSI_NULLS ON
//GO

//SET QUOTED_IDENTIFIER ON
//GO

//CREATE TABLE [dbo].[AccessControlResources](
//    [id] [int] IDENTITY(1000,1) NOT NULL,
//    [name] [nvarchar](300) NOT NULL,
//    [created_date] [datetime] NOT NULL,
//    [modified_date] [datetime] NULL,
// CONSTRAINT [PK_AccessControlResources] PRIMARY KEY CLUSTERED 
//(
//    [id] ASC
//)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
//) ON [PRIMARY]

//GO

//ALTER TABLE [dbo].[AccessControlResources] ADD  CONSTRAINT [DF_AccessControlResources_created_date]  DEFAULT (getdate()) FOR [created_date]
//GO


///****** Object:  DbTable [dbo].[AccessControlACL]    Script Date: 8/30/2012 3:37:05 PM ******/
//SET ANSI_NULLS ON
//GO

//SET QUOTED_IDENTIFIER ON
//GO

//CREATE TABLE [dbo].[AccessControlACL](
//    [resource_id] [int] NOT NULL,
//    [accessor_name] [nvarchar](100) NOT NULL,
//    [deny_access] [bit] NOT NULL,
//    [created_date] [datetime] NOT NULL,
//    [modified_date] [datetime] NULL,
// CONSTRAINT [PK_AccessControlACL] PRIMARY KEY CLUSTERED 
//(
//    [resource_id] ASC,
//    [accessor_name] ASC
//)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
//) ON [PRIMARY]

//GO

//ALTER TABLE [dbo].[AccessControlACL] ADD  CONSTRAINT [DF_Table_1_deny]  DEFAULT ((0)) FOR [deny_access]
//GO

//ALTER TABLE [dbo].[AccessControlACL] ADD  CONSTRAINT [DF_AccessControlACL_created_date]  DEFAULT (getdate()) FOR [created_date]
//GO

//ALTER TABLE [dbo].[AccessControlACL]  WITH CHECK ADD  CONSTRAINT [FK_AccessControlACL_AccessControlResources] FOREIGN KEY([resource_id])
//REFERENCES [dbo].[AccessControlResources] ([id])
//GO

//ALTER TABLE [dbo].[AccessControlACL] CHECK CONSTRAINT [FK_AccessControlACL_AccessControlResources]
//GO


// ================================ SQL ====================================
// AccessControlSql.xml

//<?xml version="1.0" encoding="utf-8"?>
//<configuration>
//    <string_resources>
//        <string resource_id="access_control_select_resource_id_by_name">
//            <value>SELECT ID FROM AccessControlResources WHERE NAME='{0}'</value>
//        </string>
//    <string resource_id="access_control_insert_resource">
//      <value version="oracle">INSERT INTO AccessControlResources (NAME, CREATED_DATE) VALUES('{0}', SYSDATE)</value>
//      <value version="sql_server">INSERT INTO AccessControlResources (NAME, CREATED_DATE) VALUES('{0}', GETDATE())</value>
//      <value>INSERT INTO AccessControlResources (NAME) VALUES('{0}')</value>
//    </string>
//    <string resource_id="access_control_insert_acl_entry">
//      <value version="oracle">INSERT INTO AccessControlACL (RESOURCE_ID, ACCESSOR_NAME, DENY_ACCESS, CREATED_DATE) VALUES({0}, '{1}', {2}, SYSDATE)</value>
//      <value version="sql_server">INSERT INTO AccessControlACL (RESOURCE_ID, ACCESSOR_NAME, DENY_ACCESS, CREATED_DATE) VALUES({0}, '{1}', {2}, GETDATE())</value>
//      <value>INSERT INTO AccessControlACL (RESOURCE_ID, ACCESSOR_NAME, DENY_ACCESS) VALUES({0}, '{1}', {2})</value>
//    </string>
//    <string resource_id="access_control_insert_acl_entry_by_resource_name">
//      <value version="oracle">
//        INSERT INTO
//        AccessControlACL
//        (RESOURCE_ID, ACCESSOR_NAME, DENY_ACCESS, CREATED_DATE)
//        VALUES ((SELECT ID FROM AccessControlResources WHERE NAME='{0}'), '{1}', {2}, SYSDATE);
//      </value>
//      <value version="sql_server">
//        INSERT INTO
//        AccessControlACL
//        (RESOURCE_ID, ACCESSOR_NAME, DENY_ACCESS, CREATED_DATE)
//        VALUES ((SELECT ID FROM AccessControlResources WHERE NAME='{0}'), '{1}', {2}, GETDATE());
//      </value>
//      <value>
//        INSERT INTO
//        AccessControlACL
//        (RESOURCE_ID, ACCESSOR_NAME, DENY_ACCESS)
//        VALUES ((SELECT ID FROM AccessControlResources WHERE NAME='{0}'), '{1}', {2});
//      </value>
//    </string>
//    <string resource_id="access_control_select_acls_by_id">
//            <value>SELECT RESOURCE_ID, ACCESSOR_NAME, DENY_ACCESS FROM AccessControlACL WHERE RESOURCE_ID={0}</value>
//        </string>
//    <string resource_id="access_control_select_acls_by_resource_name">
//            <value>
//        SELECT
//        acr.id,
//        acr.name,
//        acl.accessor_name,
//        acl.deny_access
//        FROM AccessControlResources AS acr
//        LEFT JOIN AccessControlACL AS acl ON acl.resource_id = acr.id
//        WHERE acr.name = '{0}'
//      </value>
//        </string>
//    <string resource_id="access_control_select_acls_by_resource_name_and_accessor_name">
//      <value>
//        SELECT
//        acr.id,
//        acr.name,
//        acl.accessor_name,
//        acl.deny_access
//        FROM AccessControlResources AS acr
//        LEFT JOIN AccessControlACL AS acl ON acl.resource_id = acr.id
//        WHERE acr.name = '{0}' AND acl.accessor_name = '{1}'
//      </value>
//    </string>
//    <string resource_id="access_control_delete_all_acl_entries_by_resource_id">
//      <value>DELETE FROM AccessControlACL WHERE RESOURCE_ID={0} </value>
//    </string>
//    <string resource_id="access_control_delete_all_acl_entries_by_resource_name">
//      <value>DELETE FROM AccessControlACL WHERE RESOURCE_ID=(SELECT ID FROM AccessControlResources WHERE NAME='{0}') </value>
//    </string>
//    <string resource_id="access_control_delete_acl_entry">
//      <value>DELETE FROM AccessControlACL WHERE RESOURCE_ID={0} AND ACCESSOR_NAME='{1}' </value>
//    </string>
//    <string resource_id="access_control_delete_resource_by_id">
//      <value>DELETE FROM AccessControlResources WHERE ID={0} </value>
//    </string>
//    <string resource_id="access_control_delete_resource_by_name">
//      <value>DELETE FROM AccessControlResources WHERE NAME='{0}' </value>
//    </string>

//  </string_resources>

//</configuration>
